using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Nuuru.Tools.AuthFlowChart.Models; namespace Nuuru.Tools.AuthFlowChart.Analysis; /// /// Detects inline permission checks like User.HasPermission() in method bodies /// public class InlineCheckVisitor : CSharpSyntaxWalker { private readonly List _checks = new(); public IReadOnlyList Checks => _checks; public static IReadOnlyList ExtractFromMethod(MethodDeclarationSyntax method) { var visitor = new InlineCheckVisitor(); visitor.Visit(method.Body); visitor.Visit(method.ExpressionBody); return visitor.Checks; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { // Look for User.HasPermission() or .HasPermission() calls if (node.Expression is MemberAccessExpressionSyntax memberAccess) { var methodName = memberAccess.Name.Identifier.Text; if (methodName == "HasPermission" || methodName == "HasClaim") { var permission = ExtractPermissionArgument(node, methodName); if (!string.IsNullOrEmpty(permission)) { _checks.Add(new PermissionCheck(permission, PermissionSource.Inline)); } } } base.VisitInvocationExpression(node); } private static string? ExtractPermissionArgument(InvocationExpressionSyntax invocation, string methodName) { var args = invocation.ArgumentList.Arguments; if (args.Count == 0) return null; // For HasPermission, the first argument is the permission // For HasClaim, the second argument is the permission value (first is claim type) var argIndex = methodName == "HasClaim" && args.Count >= 2 ? 1 : 0; if (argIndex >= args.Count) return null; var arg = args[argIndex].Expression; // Handle string literal: "user.upload_post" if (arg is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression)) { return literal.Token.ValueText; } // Handle permission constant: Permissions.User.UploadPost if (arg is MemberAccessExpressionSyntax memberAccess) { var fullName = memberAccess.ToString(); return ResolvePermissionConstant(fullName); } // Handle identifier (variable containing permission) if (arg is IdentifierNameSyntax identifier) { // We can't resolve variable values statically, but we note it return $"[{identifier.Identifier.Text}]"; } return null; } /// /// Resolve permission constants like "Permissions.User.UploadPost" to their string values /// private static string ResolvePermissionConstant(string value) { // If it's already a string value, return it if (!value.StartsWith("Permissions.")) return value; // Map known permission constants to their values return value switch { "Permissions.User.UploadPost" => "user.upload_post", "Permissions.User.Comment" => "user.comment", "Permissions.User.EditOwnContent" => "user.edit_own_content", "Permissions.User.DeleteOwnContent" => "user.delete_own_content", "Permissions.User.EditTags" => "user.edit_tags", "Permissions.Moderation.TrashPost" => "moderation.trash_post", "Permissions.Moderation.DeleteComment" => "moderation.delete_comment", "Permissions.Moderation.EditTags" => "moderation.edit_tags", "Permissions.Moderation.BanUser" => "moderation.ban_user", "Permissions.Moderation.ViewReports" => "moderation.view_reports", "Permissions.Moderation.ViewAuditLog" => "moderation.view_audit_log", "Permissions.Admin.ManageUsers" => "admin.manage_users", "Permissions.Admin.ManagePermissions" => "admin.manage_permissions", "Permissions.Admin.SystemSettings" => "admin.system_settings", _ => value }; } }