Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace OpenRiaServices.Server.Authentication
/// <summary>
/// <see cref="CodeProcessor"/> implementation that sets the base class of both the
/// context and entity types generated by a provider implementing
/// <see cref="IAuthentication{T}"/>.
/// <see cref="IAuthentication{T}"/>. or
/// <see cref="IAuthenticationAsync{T}"/>.
/// </summary>
internal sealed class AuthenticationCodeProcessor : CodeProcessor
{
Expand Down Expand Up @@ -263,19 +264,19 @@ internal static CodeCommentStatementCollection GetDocComments(string resourceCom
}

/// <summary>
/// Validates that the authentication service implements the <see cref="IAuthentication{T}"/> interface
/// Validates that the authentication service implements <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/> interface
/// naturally for use in codegen.
/// </summary>
/// <remarks>
/// This check ensures no part of the interface was implemented explicitly.
/// </remarks>
/// <param name="authenticationServiceDescription">The domain service description for the type that implemented
/// the <see cref="IAuthentication{T}"/> interface.
/// the <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/> interface.
/// </param>
/// <param name="genericIAuthenticationType">The generic version of <see cref="IAuthentication{T}"/> implemented
/// <param name="genericIAuthenticationType">The generic version of <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/> implemented
/// by the service type of the <paramref name="authenticationServiceDescription"/>.
/// </param>
/// <exception cref="InvalidOperationException"> is thrown if the <see cref="IAuthentication{T}"/> interface
/// <exception cref="InvalidOperationException"> is thrown if the <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/> interface
/// is not correctly implemented.
/// </exception>
private static void CheckIAuthentication(DomainServiceDescription authenticationServiceDescription, out Type genericIAuthenticationType)
Expand All @@ -285,7 +286,7 @@ private static void CheckIAuthentication(DomainServiceDescription authentication
bool implementsGetUser = false;
bool implementsUpdateUser = false;

if (!typeof(IAuthentication<>).DefinitionIsAssignableFrom(authenticationServiceDescription.DomainServiceType, out genericIAuthenticationType))
if (!authenticationServiceDescription.TryGetAuthenticationServiceType(out genericIAuthenticationType))
{
throw new InvalidOperationException(Resources.ApplicationServices_MustBeIAuth);
}
Expand All @@ -296,16 +297,16 @@ private static void CheckIAuthentication(DomainServiceDescription authentication
{
switch (doe.Name)
{
case "Login":
case string name when name.StartsWith("Login", StringComparison.OrdinalIgnoreCase):
implementsLogin = CheckIAuthenticationLogin(doe, userType);
break;
case "Logout":
case string name when name.StartsWith("Logout", StringComparison.OrdinalIgnoreCase):
implementsLogout = CheckIAuthenticationLogout(doe, userType);
break;
case "GetUser":
case string name when name.StartsWith("GetUser", StringComparison.OrdinalIgnoreCase):
implementsGetUser = CheckIAuthenticationGetUser(doe, userType);
break;
case "UpdateUser":
case string name when name.StartsWith("UpdateUser", StringComparison.OrdinalIgnoreCase):
implementsUpdateUser = CheckIAuthenticationUpdateUser(doe, userType);
break;
default:
Expand All @@ -323,10 +324,10 @@ private static void CheckIAuthentication(DomainServiceDescription authentication
}

/// <summary>
/// Validates that the operation entry represents <see cref="IAuthentication{T}.Login"/> for use in codegen.
/// Validates that the operation entry represents <see cref="IAuthentication{T}.Login"/> or <see cref="IAuthenticationAsync{T}.LoginAsync"/> for use in codegen.
/// </summary>
/// <param name="doe">The entry to validate</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/>.</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/>.</param>
/// <returns>Whether the operation entry represents Login</returns>
private static bool CheckIAuthenticationLogin(DomainOperationEntry doe, Type userType)
{
Expand Down Expand Up @@ -355,10 +356,10 @@ private static bool CheckIAuthenticationLogin(DomainOperationEntry doe, Type use
}

/// <summary>
/// Validates that the operation entry represents <see cref="IAuthentication{T}.Logout"/> for use in codegen.
/// Validates that the operation entry represents <see cref="IAuthentication{T}.Logout"/> or <see cref="IAuthenticationAsync{T}.LogoutAsync"/> for use in codegen.
/// </summary>
/// <param name="doe">The entry to validate</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/>.</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/>.</param>
/// <returns>Whether the operation entry represents Logout</returns>
private static bool CheckIAuthenticationLogout(DomainOperationEntry doe, Type userType)
{
Expand All @@ -383,10 +384,10 @@ private static bool CheckIAuthenticationLogout(DomainOperationEntry doe, Type us
}

/// <summary>
/// Validates that the operation entry represents <see cref="IAuthentication{T}.GetUser"/> for use in codegen.
/// Validates that the operation entry represents <see cref="IAuthentication{T}.GetUser"/> or <see cref="IAuthenticationAsync{T}.GetUserAsync"/> for use in codegen.
/// </summary>
/// <param name="doe">The entry to validate</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/>.</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/>.</param>
/// <returns>Whether the operation entry represents GetUser</returns>
private static bool CheckIAuthenticationGetUser(DomainOperationEntry doe, Type userType)
{
Expand All @@ -411,10 +412,10 @@ private static bool CheckIAuthenticationGetUser(DomainOperationEntry doe, Type u
}

/// <summary>
/// Validates that the operation entry represents <see cref="IAuthentication{T}.UpdateUser"/> for use in codegen.
/// Validates that the operation entry represents <see cref="IAuthentication{T}.UpdateUser"/> or <see cref="IAuthenticationAsync{T}.UpdateUserAsync"/>for use in codegen.
/// </summary>
/// <param name="doe">The entry to validate</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/>.</param>
/// <param name="userType">The user type. <c>T</c> in <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/>.</param>
/// <returns>Whether the operation entry represents UpdateUser</returns>
private static bool CheckIAuthenticationUpdateUser(DomainOperationEntry doe, Type userType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace OpenRiaServices.Server.Authentication
/// </summary>
/// <remarks>
/// This attribute is used to associate the <see cref="AuthenticationCodeProcessor"/> with
/// an implementation of the <see cref="IAuthentication{T}"/> interface.
/// an implementation of the <see cref="IAuthentication{T}"/> and <see cref="IAuthenticationAsync{T}"/> interface.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public sealed class AuthenticationServiceAttribute : DomainIdentifierAttribute
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Threading.Tasks;

namespace OpenRiaServices.Server.Authentication
{
/// <summary>
/// An interface for a <see cref="DomainService"/> that encapsulates the authentication domain. A
/// domain service implementing this interface will be used to populate the user on the client.
/// </summary>
/// <remarks>
/// <c>OpenRiaServices.Client.Authentication.WebAuthenticationService</c>
/// will work with the <c>DomainContext</c> generated for any domain service implementing this
/// interface.
/// <para>
/// <see cref="UpdateUserAsync"/> is designed as an update method, and will be invoked via
/// <c>SubmitChanges</c> on the client. This has a couple implications. First,
/// invoking <see cref="UpdateUserAsync"/> via <c>AuthenticationService.SaveUser</c> will submit all
/// changes that have occurred in the <c>DomainContext</c> and may invoke other update methods.
/// Second, invoking other update methods on the <c>DomainContext</c> from the client will submit
/// all changes and may invoke <see cref="UpdateUserAsync"/>.
/// </para>
/// </remarks>
/// <typeparam name="T">The type of the user entity</typeparam>
[AuthenticationService]
public interface IAuthenticationAsync<T> where T : IUser
{
/// <summary>
/// Authenticates and returns the user with the specified name and password.
/// </summary>
/// <remarks>
/// This method will return a single user if the
/// authentication was successful. If the user could not be authenticated, <c>null</c>
/// will be returned.
/// </remarks>
/// <param name="userName">The userName associated with the user to authenticate</param>
/// <param name="password">The password associated with the user to authenticate</param>
/// <param name="isPersistent">Whether the authentication should persist between sessions</param>
/// <param name="customData">Optional implementation-specific data</param>
/// <returns>A single user or <c>null</c> if authentication failed</returns>
[Query(IsComposable = false, HasSideEffects = true)]
Task<T> LoginAsync(string userName, string password, bool isPersistent, string customData);

/// <summary>
/// Logs an authenticated user out.
/// </summary>
/// <remarks>
/// This method will return a single, anonymous user.
/// </remarks>
/// <returns>A single, default user.</returns>
[Query(IsComposable = false, HasSideEffects = true)]
Task<T> LogoutAsync();

/// <summary>
/// Gets the principal and profile for the current user.
/// </summary>
/// <remarks>
/// This method will return a single user. If the user is not
/// authenticated, an anonymous user will be returned.
/// </remarks>
/// <returns>An enumerable with a single user.</returns>
[Query(IsComposable = false)]
Task<T> GetUserAsync();

/// <summary>
/// Updates the profile for the authenticated user.
/// </summary>
/// <param name="user">The updated user</param>
/// <exception cref="System.UnauthorizedAccessException"> is thrown if the authenticated
/// user does not have the correct permissions to update the profile.
/// </exception>
[Update]
[RequiresAuthentication]
Task UpdateUserAsync(T user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace OpenRiaServices.Server.Authentication
/// Interface for user entities that has properties for passing principal values to the client.
/// </summary>
/// <remarks>
/// This class is designed for use with the <see cref="IAuthentication{T}"/> interface.
/// This class is designed for use with the <see cref="IAuthentication{T}"/> or <see cref="IAuthenticationAsync{T}"/> interface.
/// It provides properties to support serialization of principal values to an entity class
/// generated from a type implementing this interface.
/// </remarks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using OpenRiaServices.Server.Authentication;

namespace OpenRiaServices.Server
{
Expand Down Expand Up @@ -2081,5 +2082,13 @@ private HashSet<Type> GetOrCreateRootEntityTypes()
}
return this._rootEntityTypes;
}

public bool IsAuthenticationService() => TryGetAuthenticationServiceType(out var _);

public bool TryGetAuthenticationServiceType(out Type genericType)
{
return typeof(IAuthentication<>).DefinitionIsAssignableFrom(DomainServiceType, out genericType)
|| typeof(IAuthenticationAsync<>).DefinitionIsAssignableFrom(DomainServiceType, out genericType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenRiaServices.Server.Authentication;
using System.Threading.Tasks;

namespace OpenRiaServices.Server.Test.Authentication
{
Expand Down Expand Up @@ -214,26 +215,26 @@ private static void Generate<T>()
null /* unused in negative tests */);
}

public class AcpDomainService<T> : DomainService, IAuthentication<T> where T : IUser
public class AcpDomainService<T> : DomainService, IAuthenticationAsync<T> where T : IUser
{
#region IAuthentication<T> Members

public T Login(string userName, string password, bool isPersistent, string customData)
public Task<T> LoginAsync(string userName, string password, bool isPersistent, string customData)
{
throw new NotImplementedException();
}

public T Logout()
public Task<T> LogoutAsync()
{
throw new NotImplementedException();
}

public T GetUser()
public Task<T> GetUserAsync()
{
throw new NotImplementedException();
}

public void UpdateUser(T user)
public Task UpdateUserAsync(T user)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private void GenerateUsings()
protected virtual void GenerateClassDeclaration()
{
string baseType = "OpenRiaServices.Client.DomainContext";
if(typeof(IAuthentication<>).DefinitionIsAssignableFrom(this.DomainServiceDescription.DomainServiceType))
if(DomainServiceDescription.IsAuthenticationService())
{
baseType = @"global::OpenRiaServices.Client.Authentication.AuthenticationDomainContextBase";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,10 @@ protected virtual void GenerateProperties()


DomainServiceDescription defaultAuthDescription = this.GetDefaultAuthDescription();
if(defaultAuthDescription != null)
if(defaultAuthDescription != null
&& defaultAuthDescription.TryGetAuthenticationServiceType(out Type genericType)
&& (genericType.GetGenericArguments().Count() == 1))
{
Type genericType = null;
typeof(IAuthentication<>).DefinitionIsAssignableFrom(defaultAuthDescription.DomainServiceType, out genericType);
if ((genericType != null) && (genericType.GetGenericArguments().Count() == 1))
{
string typeName = CodeGenUtilities.GetTypeName(genericType.GetGenericArguments()[0]);

this.Write("public new ");
Expand All @@ -112,8 +110,6 @@ protected virtual void GenerateProperties()

this.Write(")base.User; }\r\n}\r\n");


}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private DomainServiceDescription GetDefaultAuthDescription()
{
DomainServiceDescription defaultAuthDescription = null;
IEnumerable<DomainServiceDescription> authDescriptions =
this.DomainServiceDescriptions.Where(d => typeof(IAuthentication<>).DefinitionIsAssignableFrom(d.DomainServiceType));
this.DomainServiceDescriptions.Where(d => d.IsAuthenticationService());
if (authDescriptions.Count() > 1)
{
this.ClientCodeGenerator.CodeGenerationHost.LogMessage(
Expand Down
44 changes: 21 additions & 23 deletions src/OpenRiaServices.Tools/Framework/WebContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public override void Generate()

// Find the AuthenticationServices and if there's just one, use it as the default.
IEnumerable<DomainServiceDescription> authDescriptions =
this.ClientProxyGenerator.DomainServiceDescriptions.Where(d => typeof(IAuthentication<>).DefinitionIsAssignableFrom(d.DomainServiceType));
this.ClientProxyGenerator.DomainServiceDescriptions
.Where(d => d.IsAuthenticationService());
DomainServiceDescription defaultAuthDescription = null;
if (authDescriptions.Count() > 1)
{
Expand Down Expand Up @@ -148,29 +149,26 @@ public override void Generate()
// }
// -->
// ----------------------------------------------------------------
if (defaultAuthDescription != null)
if (defaultAuthDescription != null
&& defaultAuthDescription.TryGetAuthenticationServiceType(out Type genericType)
&& (genericType.GetGenericArguments().Length == 1))
{
Type genericType = null;
typeof(IAuthentication<>).DefinitionIsAssignableFrom(defaultAuthDescription.DomainServiceType, out genericType);
if ((genericType != null) && (genericType.GetGenericArguments().Length == 1))
{
CodeMemberProperty userProperty = new CodeMemberProperty();

userProperty.Attributes = MemberAttributes.Public | MemberAttributes.New | MemberAttributes.Final;
userProperty.Type = CodeGenUtilities.GetTypeReference(
genericType.GetGenericArguments()[0], this.ClientProxyGenerator, proxyClass);
userProperty.Name = "User";
userProperty.HasGet = true;
userProperty.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeCastExpression(userProperty.Type,
new CodePropertyReferenceExpression(
new CodeBaseReferenceExpression(),
"User"))));
userProperty.Comments.AddRange(CodeGenUtilities.GetDocComments(Resource.WebContext_CommentUser, this.ClientProxyGenerator.IsCSharp));

proxyClass.Members.Add(userProperty);
}
CodeMemberProperty userProperty = new CodeMemberProperty();

userProperty.Attributes = MemberAttributes.Public | MemberAttributes.New | MemberAttributes.Final;
userProperty.Type = CodeGenUtilities.GetTypeReference(
genericType.GetGenericArguments()[0], this.ClientProxyGenerator, proxyClass);
userProperty.Name = "User";
userProperty.HasGet = true;
userProperty.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeCastExpression(userProperty.Type,
new CodePropertyReferenceExpression(
new CodeBaseReferenceExpression(),
"User"))));
userProperty.Comments.AddRange(CodeGenUtilities.GetDocComments(Resource.WebContext_CommentUser, this.ClientProxyGenerator.IsCSharp));

proxyClass.Members.Add(userProperty);
}
}
#endregion
Expand Down