Web Api found ...

This commit is contained in:
Paul Schneider
2025-02-11 04:45:05 +00:00
parent d1cadd9df8
commit 6cd5f1d041
23 changed files with 215 additions and 870 deletions

View File

@ -1,12 +1,11 @@
{
"projects": [
"src",
"scripts",
"tests"
"test"
],
"sdk": {
"runtime": "dotnet",
"architecture": "x64"
"version": "8.0.405"
},
"packages": "packages"
}

View File

@ -1,145 +0,0 @@
@inherits Microsoft.Extensions.CodeGeneration.Templating.RazorTemplateBase
@using Microsoft.Extensions.CodeGeneration.EntityFramework
@@model @Model.ViewDataTypeName
@{
if (Model.IsPartialView)
{
}
else if (Model.IsLayoutPageSelected)
{
@:@@{
@:ViewData["Title"] = @@Model.ViewName;
if (!string.IsNullOrEmpty(Model.LayoutPageFile))
{
@:Layout = "@Model.LayoutPageFile";
}
@:}
@:
@:<h2>@@Model.ViewName</h2>
@:
}
else
{
@:@@{
@:Layout = "null";
@:}
@:
@:<!DOCTYPE html>
@:
@:<html>
@:<head>
@:<meta name="viewport" content="width=device-width" />
@:<title>@Model.ViewName</title>
@:</head>
@:<body>
@:
// PushIndent(" ");
}
@:<form asp-action="@Model.ViewName">
@:<div class="form-horizontal">
@:<h4>@@Model.ViewDataTypeShortName"]</h4>
@:<hr />
@:<div asp-validation-summary="ModelOnly" class="text-danger"></div>
foreach (var property in Model.ModelMetadata.Properties)
{
if (property.Scaffold && !property.IsAutoGenerated && !property.IsReadOnly)
{
// If the property is a primary key and Guid, then the Guid is generated in the controller. Hence, this propery is not displayed on the view.
if (property.IsPrimaryKey)
{
continue;
}
if (property.IsForeignKey)
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="col-md-2 control-label"></label>
@:<div class="col-md-10">
@:<select asp-for="@property.PropertyName" class ="form-control"></select>
@:</div>
@:</div>
continue;
}
bool isCheckbox = property.TypeName.Equals("System.Boolean");
if (isCheckbox)
{
@:<div class="form-group">
@:<div class="col-md-offset-2 col-md-10">
@:<div class="checkbox">
@:<input asp-for="@property.PropertyName" />
@:<label asp-for="@property.PropertyName"></label>
@:</div>
@:</div>
@:</div>
}
else if (property.IsEnum && !property.IsEnumFlags)
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="col-md-2 control-label"></label>
@:<div class="col-md-10">
@:<select asp-for="@property.PropertyName" class="form-control"></select>
@:<span asp-validation-for="@property.PropertyName" class="text-danger" ></span>
@:</div>
@:</div>
}
else
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="col-md-2 control-label"></label>
@:<div class="col-md-10">
@:<input asp-for="@property.PropertyName" class="form-control" />
@:<span asp-validation-for="@property.PropertyName" class="text-danger" ></span>
@:</div>
@:</div>
}
}
}
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">@Back to List</a>
</div>
@{
if (Model.ReferenceScriptLibraries && (Model.IsLayoutPageSelected || Model.IsPartialView))
{
@:@@section Scripts {
@:<script src="~/lib/jquery/dist/jquery.min.js"></script>
@:<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
@:<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@:}
}
// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page
if (!Model.IsPartialView && !Model.IsLayoutPageSelected)
{
if (Model.ReferenceScriptLibraries)
{
@:@@section Scripts {
@:<script src="~/lib/jquery/dist/jquery.min.js"></script>
@:<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
@:<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@:}
//ClearIndent();
}
@:</body>
@:</html>
}
}
@functions
{
// Do we need to use this in conjunction with the PrimaryKey check?
bool IsPropertyGuid(PropertyMetadata property)
{
return string.Equals("System.Guid", property.TypeName, StringComparison.OrdinalIgnoreCase);
}
}

View File

@ -1,81 +0,0 @@
@inherits Microsoft.Extensions.CodeGeneration.Templating.RazorTemplateBase
@using Microsoft.Extensions.CodeGeneration.EntityFramework
@@model @Model.ViewDataTypeName
@{
if (Model.IsPartialView)
{
}
else if (Model.IsLayoutPageSelected)
{
@:@@{
@:ViewData["Title"] = @@Model.ViewName;
if (!string.IsNullOrEmpty(Model.LayoutPageFile))
{
@:Layout = "@Model.LayoutPageFile";
}
@:}
@:
@:<h2>@@Model.ViewName</h2>
@:
}
else
{
@:@@{
@:Layout = "null";
@:}
@:
@:<!DOCTYPE html>
@:
@:<html>
@:<head>
@:<meta name="viewport" content="width=device-width" />
@:<title>@@@Model.ViewName</title>
@:</head>
@:<body>
@:
// PushIndent(" ");
}
}
<h3>@AreYourSureYouWantToDeleteThis</h3>
<div>
<h4>@@@Model.ViewDataTypeShortName</h4>
<hr />
<dl class="dl-horizontal">
@{
foreach (var property in Model.ModelMetadata.Properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
<dt>
@@Html.DisplayNameFor(model => model.@GetValueExpression(property))
</dt>
<dd>
@@Html.DisplayFor(model => model.@GetValueExpression(property))
</dd>
}
}
@:</dl>
@:
@:<form asp-action="@Model.ViewName">
@:<div class="form-actions no-color">
@:<input type="submit" value="@Delete" class="btn btn-default" /> |
@:<a asp-action="Index">Back to List</a>
@:</div>
@:</form>
@:</div>
if (!Model.IsPartialView && !Model.IsLayoutPageSelected)
{
//ClearIndent();
@:</body>
@:</html>
}
}
@functions
{
string GetValueExpression(PropertyMetadata property)
{
//Todo: Get the association for the property and use that.
return property.PropertyName;
}
}

View File

@ -1,95 +0,0 @@
@inherits Microsoft.Extensions.CodeGeneration.Templating.RazorTemplateBase
@using Microsoft.Extensions.CodeGeneration.EntityFramework
@@model @Model.ViewDataTypeName
@{
if (Model.IsPartialView)
{
}
else if (Model.IsLayoutPageSelected)
{
@:@@{
@:ViewData["Title"] = @@Model.ViewName;
if (!string.IsNullOrEmpty(Model.LayoutPageFile))
{
@:Layout = "@Model.LayoutPageFile";
}
@:}
@:
@:<h2>@@Model.ViewName</h2>
@:
}
else
{
@:@@{
@:Layout = "null";
@:}
@:
@:<!DOCTYPE html>
@:
@:<html>
@:<head>
@:<meta name="viewport" content="width=device-width" />
@:<title>@Model.ViewName</title>
@:</head>
@:<body>
@:
// PushIndent(" ");
}
}
<div>
<h4>@Model.ViewDataTypeShortName</h4>
<hr />
<dl class="dl-horizontal">
@{
foreach (var property in Model.ModelMetadata.Properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
<dt>
@@Html.DisplayNameFor(model => model.@GetValueExpression(property))
</dt>
<dd>
@@Html.DisplayFor(model => model.@GetValueExpression(property))
</dd>
}
}
} </dl>
</div>
<p>
@{
string pkName = GetPrimaryKeyName();
if (pkName != null)
{
@:<a asp-action="Edit" asp-route-id="@@Model.@pkName">@Edit</a> |
@:<a asp-action="Index">Back to List</a>
}
else
{
@:@@Html.ActionLink(@Edit, "Edit", new { /* id = Model.PrimaryKey */ }) |
@:<a asp-action="Index">Back to List</a>
}
}</p>
@{
if (!Model.IsPartialView && !Model.IsLayoutPageSelected)
{
//ClearIndent();
@:</body>
@:</html>
}
}
@functions
{
string GetPrimaryKeyName()
{
return (Model.ModelMetadata.PrimaryKeys != null && Model.ModelMetadata.PrimaryKeys.Length == 1)
? Model.ModelMetadata.PrimaryKeys[0].PropertyName
: null;
}
string GetValueExpression(PropertyMetadata property)
{
//Todo: Get the association for the property and use that.
return property.PropertyName;
}
}

View File

@ -1,149 +0,0 @@
@inherits Microsoft.Extensions.CodeGeneration.Templating.RazorTemplateBase
@using Microsoft.Extensions.CodeGeneration.EntityFramework
@@model @Model.ViewDataTypeName
@{
if (Model.IsPartialView)
{
}
else if (Model.IsLayoutPageSelected)
{
@:@@{
@:ViewData["Title"] = @@Model.ViewName;
if (!string.IsNullOrEmpty(Model.LayoutPageFile))
{
@:Layout = "@Model.LayoutPageFile";
}
@:}
@:
@:<h2>@@Model.ViewName</h2>
@:
}
else
{
@:@@{
@:Layout = "null";
@:}
@:
@:<!DOCTYPE html>
@:
@:<html>
@:<head>
@:<meta name="viewport" content="width=device-width" />
@:<title>@@Model.ViewName</title>
@:</head>
@:<body>
@:
// PushIndent(" ");
}
@:<form asp-action="@Model.ViewName">
@:<div class="form-horizontal">
@:<h4>@Model.ViewDataTypeShortName</h4>
@:<hr />
@:<div asp-validation-summary="ModelOnly" class="text-danger"></div>
foreach (PropertyMetadata property in Model.ModelMetadata.Properties)
{
if (property.Scaffold)
{
if (property.IsPrimaryKey)
{
@:<input type="hidden" asp-for="@property.PropertyName" />
continue;
}
if (property.IsReadOnly)
{
continue;
}
if (property.IsForeignKey)
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="control-label col-md-2">@GetAssociationName(property)</label>
@:<div class="col-md-10">
@:<select asp-for="@property.PropertyName" class="form-control" ></select>
@:<span asp-validation-for="@property.PropertyName" class="text-danger" ></span>
@:</div>
@:</div>
continue;
}
bool isCheckbox = property.TypeName.Equals("System.Boolean");
if (isCheckbox)
{
@:<div class="form-group">
@:<div class="col-md-offset-2 col-md-10">
@:<div class="checkbox">
@:<input asp-for="@property.PropertyName" />
@:<label asp-for="@property.PropertyName"></label>
@:</div>
@:</div>
@:</div>
}
else if (property.IsEnum && !property.IsEnumFlags)
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="col-md-2 control-label"></label>
@:<div class="col-md-10">
@:<select asp-for="@property.PropertyName" class="form-control"></select>
@:<span asp-validation-for="@property.PropertyName" class="text-danger" ></span>
@:</div>
@:</div>
}
else
{
@:<div class="form-group">
@:<label asp-for="@property.PropertyName" class="col-md-2 control-label"></label>
@:<div class="col-md-10">
@:<input asp-for="@property.PropertyName" class="form-control" />
@:<span asp-validation-for="@property.PropertyName" class="text-danger"></span>
@:</div>
@:</div>
}
}
}
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@{
if (Model.ReferenceScriptLibraries && (Model.IsLayoutPageSelected || Model.IsPartialView))
{
@:@@section Scripts {
@:<script src="~/lib/jquery/dist/jquery.min.js"></script>
@:<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
@:<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@:}
}
// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page
if (!Model.IsPartialView && !Model.IsLayoutPageSelected)
{
if (Model.ReferenceScriptLibraries)
{
@:@@section Scripts {
@:<script src="~/lib/jquery/dist/jquery.min.js"></script>
@:<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
@:<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@:}
//ClearIndent();
}
@:</body>
@:</html>
}
}
@functions
{
string GetAssociationName(PropertyMetadata property)
{
//Todo: Implement properly.
return property.PropertyName;
}
}

View File

@ -1,115 +0,0 @@
@inherits Microsoft.Extensions.CodeGeneration.Templating.RazorTemplateBase
@using Microsoft.Extensions.CodeGeneration.EntityFramework
@@model @GetEnumerableTypeExpression(Model.ViewDataTypeName)
@{
if (Model.IsPartialView)
{
}
else if (Model.IsLayoutPageSelected)
{
@:@@{
@:ViewData["Title"] = @@Model.ViewName;
if (!string.IsNullOrEmpty(Model.LayoutPageFile))
{
@:Layout = "@Model.LayoutPageFile";
}
@:}
@:
@:<h2>@@Model.ViewName</h2>
@:
}
else
{
@:@@{
@:Layout = "null";
@:}
@:
@:<!DOCTYPE html>
@:
@:<html>
@:<head>
@:<meta name="viewport" content="width=device-width" />
@:<title>@@Model.ViewName</title>
@:</head>
@:<body>
// PushIndent(" ");
}
@:<p>
@:<a asp-action="Create">Create New</a>
@:</p>
@:<table class="table">
@:<tr>
IEnumerable<PropertyMetadata> properties = Model.ModelMetadata.Properties;
foreach (var property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
<th>
@@Html.DisplayNameFor(model => model.@GetValueExpression(property))
</th>
}
}
@:<th></th>
@:</tr>
@:
@:@@foreach (var item in Model) {
@:<tr>
foreach (PropertyMetadata property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
<td>
@@Html.DisplayFor(modelItem => item.@GetValueExpression(property))
</td>
}
}
string pkName = GetPrimaryKeyName();
if (pkName != null)
{
@:<td>
@:<a asp-action="Edit" asp-route-id="@@item.@pkName">@Edit</a> |
@:<a asp-action="Details" asp-route-id="@@item.@pkName">@Details</a> |
@:<a asp-action="Delete" asp-route-id="@@item.@pkName">@Delete</a>
@:</td>
}
else
{
<td>
@@Html.ActionLink("Edit", "Edit",new { /* id=item.PrimaryKey */ }) |
@@Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
@@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
}
@:</tr>
@:}
@:</table>
if(!Model.IsPartialView && !Model.IsLayoutPageSelected)
{
//ClearIndent();
@:</body>
@:</html>
}
}
@functions
{
string GetPrimaryKeyName()
{
return (Model.ModelMetadata.PrimaryKeys != null && Model.ModelMetadata.PrimaryKeys.Length == 1)
? Model.ModelMetadata.PrimaryKeys[0].PropertyName
: null;
}
string GetValueExpression(PropertyMetadata property)
{
//Todo: Get the association for the property and use that.
return property.PropertyName;
}
string GetEnumerableTypeExpression(string typeName)
{
return "IEnumerable<" + typeName + ">";
}
}

View File

@ -1,78 +0,0 @@
include versioning.mk
REPO_ROOT=../../../src
SUBDIRS=Yavsc Yavsc.Server Yavsc.Abstract OAuth.AspNet.AuthServer OAuth.AspNet.Token cli test
all: $(SUBDIRS)
Yavsc.Abstract:
$(MAKE) -C $(REPO_ROOT)/Yavsc.Abstract VERSION=$(VERSION)
OAuth.AspNet.Token:
$(MAKE) -C $(REPO_ROOT)/OAuth.AspNet.Token VERSION=$(VERSION)
OAuth.AspNet.AuthServer: OAuth.AspNet.Token
$(MAKE) -C $(REPO_ROOT)/OAuth.AspNet.AuthServer VERSION=$(VERSION)
Yavsc.Server: Yavsc.Abstract
$(MAKE) -C $(REPO_ROOT)/Yavsc.Server VERSION=$(VERSION)
Yavsc: Yavsc.Server OAuth.AspNet.AuthServer OAuth.AspNet.Token
make -C $(REPO_ROOT)/Yavsc VERSION=$(VERSION)
Yavsc-deploy-pkg: Yavsc
make -C $(REPO_ROOT)/Yavsc deploy-pkg
Yavsc.Server-deploy-pkg: Yavsc.Server
make -C $(REPO_ROOT)/Yavsc.Server deploy-pkg
Yavsc.Abstract-deploy-pkg: Yavsc.Abstract
make -C $(REPO_ROOT)/Yavsc.Abstract deploy-pkg
cli-deploy-pkg: cli check
make -C $(REPO_ROOT)/cli deploy-pkg
cli: Yavsc-deploy-pkg Yavsc.Server-deploy-pkg Yavsc.Abstract-deploy-pkg
make -C $(REPO_ROOT)/cli
undoLocalYavscNugetDeploy:
rm -rf ../../../packages/Yavsc.Abstract
rm -rf ../../../packages/Yavsc.Server
rm -rf ../../../packages/Yavsc
rm -rf ~/.dnx/packages/Yavsc.Abstract
rm -rf ~/.dnx/packages/Yavsc.Server
rm -rf ~/.dnx/packages/Yavsc
check: cli
make -C $(REPO_ROOT)/cli check
make -C $(REPO_ROOT)/test
test:
make -C $(REPO_ROOT)/test
pushInPre:
make -C $(REPO_ROOT)/Yavsc pushInPre
pushInProd:
make -C $(REPO_ROOT)/Yavsc pushInProd
deploy-pkgs: Yavsc-deploy-pkg Yavsc.Server-deploy-pkg Yavsc.Abstract-deploy-pkg cli-deploy-pkg
memo:
vim ~/TODO.md
rc-num:
@echo echo 1-alpha1 < $< ^ $^ @ $@
clean:
for subdir in $(SUBDIRS) ; do \
make -C $(REPO_ROOT)/$${subdir} clean ; \
done
watch:
make -C $(REPO_ROOT)/Yavsc watch
.PHONY: all $(SUBDIRS)

View File

@ -1,71 +0,0 @@
# Common defs
#
ifndef PRJNAME
PRJNAME := $(shell basename `pwd -P`)
endif
FRAMEWORK=dnx451
ASPNET_ENV=Development
ASPNET_LOG_LEVEL=Debug
HOSTING=localhost
HOSTADMIN=root
FRAMEWORKALIAS=dnx451
# nuget package destination, at generation time
BINTARGET=$(PRJNAME).dll
BINTARGETPATH=bin/$(CONFIGURATION)/$(FRAMEWORKALIAS)/$(BINTARGET)
PKGFILENAME=$(PRJNAME).$(VERSION).nupkg
dnu=dnu
# OBS SUBDIRS=Yavsc.Server Yavsc.Abstract Yavsc cli
#
# Git commit hash, in order to not publish some uncrontrolled code in production environment
#
git_status := $(shell git status -s --porcelain |wc -l)
all: $(BINTARGETPATH)
fixSystemXML:
@# fixing package id reference case, to System.Xml, from package NJsonSchema.CodeGeneration.CSharp
@sed 's/System.XML/System.Xml/' project.lock.json > project.lock.json.new && mv project.lock.json.new project.lock.json
restore:
touch project.json
$(dnu) restore --ignore-failed-sources
project.lock.json: project.json
$(dnu) restore --ignore-failed-sources
watch: project.lock.json
MONO_OPTIONS=--debug MONO_MANAGED_WATCHER=enabled ASPNET_ENV=$(ASPNET_ENV) ASPNET_LOG_LEVEL=$(ASPNET_LOG_LEVEL) dnx-watch web --configuration=$(CONFIGURATION)
clean:
rm -rf bin obj
rm project.lock.json
cleanoutput:
rm -rf bin/$(CONFIGURATION)
rm -rf bin/output
$(BINTARGETPATH): project.lock.json rc-num.txt-check
$(dnu) build --configuration=$(CONFIGURATION)
# Default target, from one level sub dirs
bin/output:
$(dnu) publish
bin/output/wwwroot/version: bin/output
@echo $(version) > bin/output/wwwroot/version
pack: $(BINTARGETPATH) ../../version.txt
dnu pack --configuration $(CONFIGURATION)
push: pack
@echo push to source: $(ISNSOURCE)
isn push -s $(ISNSOURCE) -k $(NUGETSOURCEAPIKEY) src/$(PRJNAME)/bin/$(CONFIGURATION)/$(PRJNAME).*.nupkg
.PHONY: rc-num.txt-check
# .DEFAULT_GOAL := $(BINTARGETPATH)

View File

@ -1,16 +0,0 @@
MSBUILD=msbuild
MONO=mono
CONFIGURATION=Debug
BINTYPE=exe
PRJNAME := $(shell basename `pwd -P`)
rc_num := $(shell cat ../../rc-num.txt)
VERSION=1.0.5-rc$(rc_num)
BINTARGET=$(PRJNAME).$(BINTYPE)
BINTARGETPATH=bin/$(CONFIGURATION)/$(BINTARGET)
ISNSOURCE=$(HOME)/Nupkgs
PKGFILENAME=$(PRJNAME).$(VERSION).nupkg

View File

@ -1,16 +0,0 @@
ifndef PRJNAME
PRJNAME := $(shell basename `pwd -P`)
endif
version := $(shell cat ../../version.txt)
MAKE=make
ISNSOURCE=$(HOME)/Nupkgs
VERSION=$(version)
CONFIGURATION=Debug
version-check:
ifndef version
@echo no version number specification ... please, could you try and run 'echo 1.2.3 > ../../version.txt' ?.
else
@echo 'Got version number : $(version)'
endif

View File

@ -1,44 +0,0 @@
#!/bin/bash
version="$1"
major=0
minor=0
build=0
# break down the version number into it's components
regex="([0-9]+).([0-9]+).([0-9]+)((-[A-Za-z]+)([0-9]+))?"
if [[ $version =~ $regex ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
build="${BASH_REMATCH[3]}"
patchtype="${BASH_REMATCH[5]}"
patchnum="${BASH_REMATCH[6]}"
fi
# check paramater to see which number to increment
if [[ "$2" == "feature" ]]; then
minor=$(echo $minor + 1 | bc)
build=0
patchtype=
patchnum=
elif [[ "$2" == "build" ]]; then
build=$(echo $build + 1 | bc)
patchtype=
patchnum=
elif [[ "$2" == "major" ]]; then
major=$(echo $major+1 | bc)
minor=0
build=0
patchtype=
patchnum=
elif [[ "$2" == "patch" ]]; then
patchnum=$(echo $patchnum + 1 | bc)
else
echo "usage: ./version.sh version_number [major/feature/build/patch]" >&2
exit -1
fi
# echo the new version number
echo "${major}.${minor}.${build}${patchtype}${patchnum}"

10
src/Api/Api.csproj Normal file
View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.12" />
</ItemGroup>
</Project>

77
src/Api/Program.cs Normal file
View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/
Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
Source code and license this software can be found
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
using Microsoft.AspNetCore.Mvc;
internal class Program
{
private static async Task Main(string[] args)
{
Console.Title = "API";
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
// accepts any access token issued by identity server
// adds an authorization policy for scope 'api1'
services
.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy
.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
})
.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
})
.AddControllers();
// accepts any access token issued by identity server
var authenticationBuilder = services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
using (var app = builder.Build())
{
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors("default");
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
).RequireAuthorization("ApiScope");
await app.RunAsync();
}
}
}

10
src/Api/appsettings.json Normal file
View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -77,10 +77,12 @@ public static class Config
PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"scope2" }
},
};

View File

@ -2,6 +2,7 @@ using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Util.Store;
using IdentityServer8;
using IdentityServer8.Hosting;
using IdentityServer8.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
@ -187,8 +188,9 @@ internal static class HostingExtensions
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>()
.AddJwtBearerClientAuthentication()
;
services.AddScoped<IProfileService, ProfileService>();
//services.AddScoped<IProfileService, ProfileService>();
if (builder.Environment.IsDevelopment())
{
@ -207,7 +209,15 @@ internal static class HostingExtensions
// TODO .AddServerSideSessionStore<YavscServerSideSessionStore>()
var authenticationBuilder = services.AddAuthentication();
var authenticationBuilder = services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails=true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
authenticationBuilder.AddGoogle(options =>
{
@ -251,13 +261,7 @@ internal static class HostingExtensions
};
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
_ = builder.WithOrigins("*");
});
});
// Add the system clock service
@ -313,10 +317,10 @@ internal static class HostingExtensions
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
{
policy.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
options.AddPolicy("Performer", policy =>
{
policy
@ -334,11 +338,29 @@ internal static class HostingExtensions
// options.AddPolicy("BuildingEntry", policy => policy.Requirements.Add(new OfficeEntryRequirement()));
options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());
options.AddPolicy("IsTheAuthor", policy => policy.Requirements.Add(new EditPermission()));
})
.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
_ = builder.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyMethod();
});
options.AddPolicy("default", policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
// accepts any access token issued by identity server
return builder.Build();
}
public static WebApplication ConfigurePipeline(this WebApplication app)
@ -357,9 +379,6 @@ internal static class HostingExtensions
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.MapGet("/api/me", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
).RequireAuthorization("ApiScope");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

View File

@ -8,11 +8,11 @@ using Yavsc.Models;
namespace Yavsc.Services
{
public class ProfileService : IProfileService
public class ProfileService : DefaultProfileService, IProfileService
{
private readonly UserManager<ApplicationUser> _userManager;
public ProfileService(
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager, ILogger<DefaultProfileService> logger) : base(logger)
{
_userManager = userManager;
}
@ -21,50 +21,56 @@ namespace Yavsc.Services
ProfileDataRequestContext context,
ApplicationUser user)
{
var requestedApiResources = context.RequestedResources.Resources.ApiResources.Select(
r => r.Name
).ToArray();
var requestedApiScopes = context.RequestedResources.Resources.ApiScopes.Select(
s => s.Name
).ToArray();
var allowedScopes = context.Client.AllowedScopes
.Where(s => s != JwtClaimTypes.Subject)
var requestedScopes = context.Client.AllowedScopes
.Where(s => s != JwtClaimTypes.Subject
&& requestedApiScopes.Contains(s))
.ToList();
if (allowedScopes.Contains("profile"))
if (context.RequestedClaimTypes.Contains("profile"))
if (requestedScopes.Contains("profile"))
{
allowedScopes.Remove("profile");
allowedScopes.Add(JwtClaimTypes.Name);
allowedScopes.Add(JwtClaimTypes.FamilyName);
allowedScopes.Add(JwtClaimTypes.Email);
allowedScopes.Add(JwtClaimTypes.PreferredUserName);
allowedScopes.Add("http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
requestedScopes.Remove("profile");
requestedScopes.Add(JwtClaimTypes.Name);
requestedScopes.Add(JwtClaimTypes.FamilyName);
requestedScopes.Add(JwtClaimTypes.Email);
requestedScopes.Add(JwtClaimTypes.PreferredUserName);
requestedScopes.Add(JwtClaimTypes.Role);
}
var claims = new List<Claim> {
new Claim(JwtClaimTypes.Subject,user.Id.ToString()),
};
foreach (var subClaim in context.Subject.Claims)
if (requestedScopes.Contains(JwtClaimTypes.Name)||
requestedScopes.Contains(JwtClaimTypes.FamilyName))
{
if (allowedScopes.Contains(subClaim.Type))
claims.Add(subClaim);
claims.Add(new Claim(JwtClaimTypes.Name, user.FullName));
}
AddClaims(allowedScopes, claims, JwtClaimTypes.Email, user.Email);
AddClaims(allowedScopes, claims, JwtClaimTypes.PreferredUserName, user.FullName);
foreach (var scope in context.Client.AllowedScopes)
if (requestedScopes.Contains(JwtClaimTypes.PreferredUserName) )
{
claims.Add(new Claim("scope", scope));
claims.Add(new Claim(JwtClaimTypes.Name, user.UserName));
}
if (requestedScopes.Contains(JwtClaimTypes.Email))
claims.Add(new Claim(JwtClaimTypes.Email, user.Email));
if (requestedScopes.Contains(JwtClaimTypes.Role))
{
var roles = await this._userManager.GetRolesAsync(user);
if (roles.Count()>0)
{
claims.Add(new Claim(JwtClaimTypes.Role,String.Join(" ",roles)));
}
}
return claims;
}
private static void AddClaims(List<string> allowedScopes, List<Claim> claims,
string claimType, string claimValue
)
{
if (allowedScopes.Contains(claimType))
if (!claims.Any(c => c.Type == claimType))
claims.Add(new Claim(claimType, claimValue));
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
@ -72,7 +78,6 @@ namespace Yavsc.Services
context.IssuedClaims = await GetClaimsFromUserAsync(context, user);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;

View File

@ -54,14 +54,31 @@ namespace testOauthClient.Controllers
public async Task<IActionResult> GetUserInfo(CancellationToken cancellationToken)
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false });
client.DefaultRequestHeaders.Add("Accept", "application/json");
var content = await client.GetStringAsync("https://localhost:5001/api/account/me");
var obj = JsonSerializer.Deserialize<JsonElement>(content);
client.SetBearerToken(accessToken);
var content = await client.GetAsync("https://localhost:5001/api/account/me");
content.EnsureSuccessStatusCode();
var json = await content.Content.ReadAsStreamAsync();
var obj = JsonSerializer.Deserialize<JsonElement>(json);
return View("UserInfo", obj.ToString());
}
[HttpPost]
public async Task<IActionResult> GetApiCall(CancellationToken cancellationToken)
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false });
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.SetBearerToken(accessToken);
var content = await client.GetAsync("https://localhost:6001/identity");
content.EnsureSuccessStatusCode();
var json = await content.Content.ReadAsStreamAsync();
var obj = JsonSerializer.Deserialize<JsonElement>(json);
return View("UserInfo", obj.ToString());
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";

View File

@ -4,8 +4,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:16967",
"sslPort": 44387
"applicationUrl": "http://localhost:5002",
"sslPort": 5003
}
},
"profiles": {
@ -13,7 +13,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5164",
"applicationUrl": "http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -22,7 +22,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7055;http://localhost:5164",
"applicationUrl": "https://localhost:5003;http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -23,7 +23,6 @@ public class Startup
.AddOpenIdConnect("Yavsc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.ResponseType = "code";
@ -32,6 +31,8 @@ public class Startup
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.Scope.Add("scope2");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
@ -44,6 +45,7 @@ public class Startup
NameClaimType = "name",
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
};
});
}

View File

@ -21,6 +21,11 @@
<form action="~/Home/GetUserInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Get user info</button>
</form>
<form action="~/Home/GetApiCall" method="post">
<button class="btn btn-lg btn-warning" type="submit">Api Call</button>
</form>
<form action="~/Home/PostDeviceInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Post device info</button>
</form>

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.12" />
</ItemGroup>
<ItemGroup>
@ -10,6 +11,7 @@
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>

View File

@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc", "src\Yavsc\Yavsc.cs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sampleWebAsWebApiClient", "src\sampleWebAsWebApiClient\sampleWebAsWebApiClient.csproj", "{38AA74FE-3932-49C3-A382-338E7C861BB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "src\Api\Api.csproj", "{16CCC793-BF57-4E8B-9BB5-76283379BA3F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -40,11 +42,16 @@ Global
{38AA74FE-3932-49C3-A382-338E7C861BB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38AA74FE-3932-49C3-A382-338E7C861BB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38AA74FE-3932-49C3-A382-338E7C861BB1}.Release|Any CPU.Build.0 = Release|Any CPU
{16CCC793-BF57-4E8B-9BB5-76283379BA3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16CCC793-BF57-4E8B-9BB5-76283379BA3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16CCC793-BF57-4E8B-9BB5-76283379BA3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16CCC793-BF57-4E8B-9BB5-76283379BA3F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5AFB6255-CF1B-4660-BB35-F24C8C75FECE} = {503DDD6B-BE10-4235-9EBD-E9B1FA6067DF}
{830F5A71-0192-4288-9F4D-D7849D958970} = {503DDD6B-BE10-4235-9EBD-E9B1FA6067DF}
{87DABC88-C38C-45DB-8F72-53B7C95ABC89} = {503DDD6B-BE10-4235-9EBD-E9B1FA6067DF}
{38AA74FE-3932-49C3-A382-338E7C861BB1} = {503DDD6B-BE10-4235-9EBD-E9B1FA6067DF}
{16CCC793-BF57-4E8B-9BB5-76283379BA3F} = {503DDD6B-BE10-4235-9EBD-E9B1FA6067DF}
EndGlobalSection
EndGlobal